package com.jeremyaburns.oddio.tracker 
{
	import com.jeremyaburns.oddio.Instrument;
	import com.jeremyaburns.oddio.Song;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	
	internal class OTWaveEditor extends Sprite 
	{
		[Embed(source="wavetools.png")] static private const WAVETOOLS_PNG :Class;
		
		static public const WIDTH  :Number = 620;
		static public const HEIGHT :Number = 420;
		
		private const WAVE_WIDTH  :Number = 600;
		private const WAVE_HEIGHT :Number = 400;
		
		private const TOOL_PEN   :int = 0;
		private const TOOL_LINE  :int = 1;
		private const TOOL_CURVE :int = 2;
		private const TOOL_FILL  :int = 3;
		private const TOOL_SINE  :int = 4;
		private const TOOL_DONE  :int = 5
		
		private const TOOL_COUNT :int = 6;
		
		
		public var song :Song;
		
		private var mWaveHolder :Sprite;
		private var mInstrument :Instrument;
		private var mInstrumentIndex :int;
		
		private var mWaveToolsPng    :BitmapData;
		private var mWaveToolsSprite :Sprite; // This needs to hold the Bitmap so that we can capture mouse events.
		private var mWaveTools       :Bitmap;
		private var mCurTool         :int;
		
		private var mLastMouseEvent :MouseEvent;
		
		private var mMouseDown :Boolean;
		
		
		public function get instrumentIndex() :int { return mInstrumentIndex; }
		
		
		public function OTWaveEditor( song:Song ) 
		{
			this.song = song;
			
			graphics.lineStyle();
			graphics.beginFill( 0, 0 );
			graphics.drawRect( -1000 , -1000 , 2000 , 2000 );
			graphics.beginFill( 0x222222 );
			graphics.drawRect( -WIDTH/2 , -HEIGHT/2 , WIDTH+30 , HEIGHT );
			graphics.beginFill( 0x111111 );
			graphics.drawRect( -WAVE_WIDTH/2 , -WAVE_HEIGHT/2 , WAVE_WIDTH , WAVE_HEIGHT );
			graphics.endFill();
			graphics.lineStyle( 1, 0x1A1A1A );
			graphics.moveTo( -WAVE_WIDTH/4, -WAVE_HEIGHT/2 );
			graphics.lineTo( -WAVE_WIDTH/4,  WAVE_HEIGHT/2 );
			graphics.moveTo(  WAVE_WIDTH/4, -WAVE_HEIGHT/2 );
			graphics.lineTo(  WAVE_WIDTH/4,  WAVE_HEIGHT/2 );
			graphics.lineStyle( 2, 0x222222 );
			graphics.moveTo( 0, -WAVE_HEIGHT/2 );
			graphics.lineTo( 0,  WAVE_HEIGHT/2 );
			graphics.moveTo( -WAVE_WIDTH/2 , 0 );
			graphics.lineTo(  WAVE_WIDTH/2 , 0 );
						
			mWaveHolder = new Sprite();			
			
			mWaveToolsPng = ( new WAVETOOLS_PNG() ).bitmapData;
			mWaveTools = new Bitmap( new BitmapData( 20, 20*TOOL_COUNT + 10*(TOOL_COUNT-1), true, 0 ) );
			mWaveToolsSprite = new Sprite();
			mWaveToolsSprite.addChild( mWaveTools );
			mWaveToolsSprite.x =  WAVE_WIDTH/2 + 10;
			mWaveToolsSprite.y = -WAVE_HEIGHT/2;
			mWaveToolsSprite.buttonMode = true;
			
			mCurTool = 0;
			
			addChild( mWaveHolder );
			addChild( mWaveToolsSprite );
			
			mWaveToolsSprite.addEventListener( MouseEvent.CLICK, mWaveTools_click );
			
			addEventListener( MouseEvent.MOUSE_DOWN, this_mouseDown );
			addEventListener( MouseEvent.MOUSE_MOVE, this_mouseMove );
			addEventListener( MouseEvent.MOUSE_UP,   this_mouseUp   );			
		}
		
		private function this_mouseDown( e:MouseEvent ) :void
		{
			if( e.target != this && e.target != mWaveHolder ) return;
			
			mMouseDown = true;
			
			switch( mCurTool )
			{
				case TOOL_PEN:
					this_mouseMove( e );
					break;
					
				case TOOL_LINE:	
					if( mLastMouseEvent == null ) {
						mLastMouseEvent = e;
					}
					this_mouseMove( e );
					break;
			}
		}
		
		public function show( instrumentIndex:int ) :void
		{
			mInstrumentIndex = instrumentIndex;
			mInstrument = song.instruments[instrumentIndex];

			drawWave();
			drawWaveTools();			

			visible = true;
		}
		
		public function hide() :void
		{
			visible = false;
		}
		
		private function indexFromMouseX( x:Number, allowOutsideX:Boolean=false ) :int
		{
			var index:int = mInstrument.waveform.length * (x/WAVE_WIDTH + 0.5);
			if( allowOutsideX ) { 
				if( index < 0 ) index = 0;
				if( index >= mInstrument.waveform.length ) index = mInstrument.waveform.length - 1;
			} else {
				if( index < 0 ) return -1;
				if( index >= mInstrument.waveform.length ) return -1;
			}
			return index;
		}
		private function valueFromMouseY( y:Number ) :Number
		{
			var value:Number = 2 * y / WAVE_HEIGHT;
			if( value >  1 ) value =  1;
			if( value < -1 ) value = -1;
			return value;
		}
		
		private function this_mouseMove( e:MouseEvent ) :void
		{
			if( ! mMouseDown ) return;
			
			switch( mCurTool )
			{
				case TOOL_PEN:
					if( mLastMouseEvent == null ) mLastMouseEvent = e;
					drawLine( mLastMouseEvent, e );				
					mLastMouseEvent = e;
					break;
					
				case TOOL_LINE:	
					if( mLastMouseEvent != null ) {
						drawLine( mLastMouseEvent, e, true );	
					}
					break;
			}
		}
		
		private function this_mouseUp( e:MouseEvent ) :void
		{
			mMouseDown = false;
			mLastMouseEvent = null;
			drawWave();
		}
		
		private function drawLine( event0:MouseEvent, event1:MouseEvent, allowOutsideX:Boolean=false ) :void
		{
			var x0:int    = indexFromMouseX( event0.localX, allowOutsideX );
			var x1:int    = indexFromMouseX( event1.localX, allowOutsideX );
			var y0:Number = valueFromMouseY( event0.localY );
			var y1:Number = valueFromMouseY( event1.localY );
			
			if( !allowOutsideX && ( x0 == -1 || x1 == -1 ) ) return;
			
			if( x0 == x1 ) {
				mInstrument.waveform[ x1 ] = y1;
				return;
			}
			
			if( x0 > x1 ) {
				var s0:int = x0;
				x0 = x1;
				x1 = s0;
				var s1:Number = y0;
				y0 = y1;
				y1 = s1;
			}
			
			for( var i:int = x0 ; i <= x1 ; ++i ) {
				mInstrument.waveform[i] = y0 + (y1-y0)*(Number(i-x0)/Number(x1-x0));
			}
			
			drawWave();
		}

		private function drawWave() :void
		{
			mWaveHolder.graphics.clear();
			mWaveHolder.graphics.lineStyle( 2, 0xFFFFFF );
			mWaveHolder.graphics.moveTo( -WAVE_WIDTH/2, mInstrument.waveform[0]*WAVE_HEIGHT/2 );
			for( var i:int = 0 ; i < mInstrument.waveform.length ; ++i ) {
				mWaveHolder.graphics.lineTo( -WAVE_WIDTH/2 + WAVE_WIDTH*(i/mInstrument.waveform.length) , mInstrument.waveform[i]*WAVE_HEIGHT/2 );
			}
		}
		
		private function stretchToFill() :void
		{
			var i:int;
			var max:Number = 0;
			var min:Number = 0;
			for( i = 0 ; i < mInstrument.waveform.length ; ++i ) {
				     if( mInstrument.waveform[i] > 0 && mInstrument.waveform[i] >  max ) max = mInstrument.waveform[i];
				else if( mInstrument.waveform[i] < 0 && mInstrument.waveform[i] < -min ) min = Math.abs( mInstrument.waveform[i] );
			}
			var scale:Number = 1 / ( max>min ? max : min );
			for( i = 0 ; i < mInstrument.waveform.length ; ++i ) {
				mInstrument.waveform[i] *= scale;
			}
			drawWave();
		}
		
		private function generateSine() :void
		{
			for( var i:Number = 0 ; i < mInstrument.waveform.length ; ++i ) {
				mInstrument.waveform[i] = Math.sin( 2 * Math.PI * i / mInstrument.waveform.length );
			}
			drawWave();
		}
		
		private function mWaveTools_click( e:MouseEvent ) :void
		{
			var nextTool:int = e.localY / 30;
			
			if( int(e.localY) % 30 < 20 )
			{
				mLastMouseEvent = null;
				
				switch( nextTool )
				{
					case TOOL_FILL:
						stretchToFill();
						return;
					case TOOL_SINE:
						generateSine();
						return;
					case TOOL_DONE:
						dispatchEvent( new Event( Event.COMPLETE ) );
						return;
				}
				
				mCurTool = nextTool;
				drawWaveTools();
			}
		}
		
		private function drawWaveTools() :void
		{
			var rect:Rectangle = new Rectangle( 0, 0, 20, 20 );
			var pt  :Point = new Point( 0, 0 );
			
			for( var i:int = 0 ; i < TOOL_COUNT ; ++i ) {
				rect.x = (i==mCurTool) ? 20 : 0 ;
				mWaveTools.bitmapData.copyPixels( mWaveToolsPng, rect, pt );
				rect.y += 20;
				pt.y += 30;
			}
		}
	}
}